﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.ServiceModel;
using System.Text;
using System.Windows.Forms;
using UDP_Lib;
using Voice;
using WPFMediaKit.DirectShow.Controls;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Windows.Threading;
using RemotingMessage;
using Utilities;
using System.Threading;
using System.Reflection;

namespace ServerVideoChat
{
    //[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    public class ServerVideoChatViewModel : INotifyPropertyChanged, IConference
    {
        private delegate void NoArgDelegate();

        UDP_Client UDP_IP_VIDEO;
        UDP_Client UDP_IP_AUDIO;

        public bool bUDP_Audio
        {
            get
            {
                return Properties.Settings.Default.bUDP_Audio;
            }
            set
            {
                Properties.Settings.Default.bUDP_Audio = value;
                OnPropertyChanged("bUDP_Audio");
            }
        }

        #region Events
        #endregion

        #region Properties

        public bool isUDP_Video
        {
            get { return Properties.Settings.Default.isUDP_Video; }
            set 
            {
                Properties.Settings.Default.isUDP_Video = value;
                OnPropertyChanged("isUDP_Video");
            }
        }

        private WaveFormat waveFormat = new WaveFormat(11025, 16, 2);

        private bool _bVoiceYouSelf;
        public bool bVoiceYouSelf
        {
            get { return _bVoiceYouSelf; }
            set { _bVoiceYouSelf = value; }
        }

        public string Room
        {
            get { return Properties.Settings.Default.Room; }
            set { Properties.Settings.Default.Room = value; OnPropertyChanged("Room"); }
        }

        private bool _ipAudio=true;

        public bool IpAudio
        {
            get { return _ipAudio; }
            set { _ipAudio = value; }
        }

        #region ClickOnceUpdater
        private ClickOnceExt clickOnceUpdater = new ClickOnceExt()
        {
            CheckUpdateInterval = Properties.Settings.Default.CheckUpdateInterval,
            IsRestartWhenUpdate = true,
            IsAutoUpdate = Properties.Settings.Default.IsAutoUpdate
        };
        public ClickOnceExt ClickOnceUpdater
        {
            get { return clickOnceUpdater; }
            private set
            {
                if (clickOnceUpdater == value) return;
                clickOnceUpdater = value;
                clickOnceUpdater.Restarting += (_1, _2) => { CloseModel(); };
                OnPropertyChanged("ClickOnceUpdater");
            }
        }
        #endregion

        #region IsAutoStartEnable
        public bool IsAutoStartEnable
        {
            get { return Properties.Settings.Default.IsAutoStartEnable; }
            set
            {
                Properties.Settings.Default.IsAutoStartEnable = value;
                CheckAutoStart();
                OnPropertyChanged("IsAutoStartEnable");
            }
        }
        #endregion

        #region IsAutoUpdate
        public bool IsAutoUpdate
        {
            get { return Properties.Settings.Default.IsAutoUpdate; }
            set
            {
                Properties.Settings.Default.IsAutoUpdate = value;
                ClickOnceUpdater.IsAutoUpdate = value;
                OnPropertyChanged("IsUpdateModeOn");
            }
        }
        #endregion

        #region CheckUpdateInterval
        public TimeSpan CheckUpdateInterval
        {
            get { return Properties.Settings.Default.CheckUpdateInterval; }
            set
            {
                Properties.Settings.Default.CheckUpdateInterval = value;
                ClickOnceUpdater.CheckUpdateInterval = value;
                OnPropertyChanged("CheckUpdateInterval");
            }
        }
        #endregion

        public ClientTypes ClientType
        {
            get { return Properties.Settings.Default.ClientType; }
        }

        #region Messages
        private string strMessages= string.Empty;
        public string Messages
        {
            get { return strMessages; }
            set
            {
                if (strMessages == value) return;
                strMessages = value;
                OnPropertyChanged("Messages");
            }
        }
        #endregion

        #region NetCamera
        private IP_Camera netCamera = new IP_Camera();
        public IP_Camera NetCamera
        {
            get { return netCamera; }
            set
            {
                if (netCamera == value) return;
                netCamera = value;
                OnPropertyChanged("NetCamera");
            }
        }
        #endregion

        #region IsIpVideoSending
        private bool bIsIpVideoSending = true;
        public bool IsIpVideoSending
        {
            get { return bIsIpVideoSending; }
            set
            {
                if (bIsIpVideoSending == value) return;
                bIsIpVideoSending = value;
                if (value) StartIPVideoSending();
                OnPropertyChanged("IsIpVideoSending");
            }
        }
        #endregion

        #region Clients
        private ObservableCollection<ClientInfo> clients = new ObservableCollection<ClientInfo>();
        public ObservableCollection<ClientInfo> Clients
        {
            get { return clients; }
            set
            {
                if (clients == value) return;
                clients = value;
                OnPropertyChanged("Clients");
            }
        }
        #endregion

        #region ServerName
        public string ServerName { get { return Properties.Settings.Default.ServerName; } }
        #endregion

        #region MachineName
        private string strMachineName;
        public string MachineName
        {
            get { return strMachineName; }
            private set
            {
                if (strMachineName == value) return;
                strMachineName = value;
                OnPropertyChanged("MachineName");
            }
        }
        #endregion

        #region SoftWareVersion
        private string strSoftWareVersion = "";
        public string SoftWareVersion
        {
            get { return strSoftWareVersion; }
            set
            {
                if (strSoftWareVersion == value) return;
                strSoftWareVersion = value;
                OnPropertyChanged("SoftWareVersion");
            }
        }
        #endregion

        #region IpAddres
        private string strIpAddres;
        public string IpAddres
        {
            get { return strIpAddres; }
            set
            {
                if (strIpAddres == value) return;
                strIpAddres = value;
                OnPropertyChanged("IpAddres");
            }
        }
        #endregion

        #region IPVideoSample
        private byte[] btIPVideoSample;
        public byte[] IPVideoSample
        {
            get { return btIPVideoSample; }
            set
            {
                if (btIPVideoSample == value) return;
                btIPVideoSample = value;
                OnPropertyChanged("IPVideoSample");
            }
        }
        #endregion

        #region IPVideoSendInterval
        public int IPVideoSendInterval
        {
            get { return Properties.Settings.Default.IPVideoSendInterval; }
            set
            {
                Properties.Settings.Default.IPVideoSendInterval = value;
                OnPropertyChanged("IPVideoSendInterval");
            }
        }
        #endregion

        #region Participant
        private IConferenceChannel participant;
        public IConferenceChannel Participant
        {
            get { return participant; }
        }
        #endregion

        #region IsPinging
        private bool isPinging;
        public bool IsPinging
        {
            get { return isPinging; }
            set
            {
                isPinging = value;
                OnPropertyChanged("IsPinging");
                if (isPinging) StartPing();
            }
        }
        #endregion
        
        #endregion

        #region Methods
        public ServerVideoChatViewModel()
        {
            try
            {
                UDP_IP_VIDEO = new UDP_Client() { ServerIP = "224.0.0.1", ServerPort = 5000 };
                UDP_IP_VIDEO.Start();
                UDP_IP_AUDIO = new UDP_Client() { ServerIP = "224.0.0.1", ServerPort = 6000 };
                UDP_IP_AUDIO.Start();
            }
            catch (Exception ex)
            {
                MessageBox.Show("Ошибка создания UDP серверов\n" + ex.ToString());
                //throw;
            }

            try
            {
                if (waveIn == null)
                {
                    if(WaveInRecorder.Devices.Length>0)
                    {
                        WaveIn = new WaveInRecorder(-1, waveFormat, 4096, 2);
                        WaveIn.DevRecordIn = WaveInRecorder.Devices[0];
                    }
                    else
                        WaveIn = new WaveInRecorder(-1, waveFormat, 4096, 2);
                }

            }
            catch (Exception)
            {

            }

            try
            {
                netCamera.NewImage += (_1, e) =>
                {
                    if (Participant != null && Participant.State == CommunicationState.Opened)
                    {
                        lock (this)
                        {
                            IPVideoSample = e.ImageBuffer.Clone() as byte[];
                        }
                    }
                };
                tmrPing = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(1) };
                StartIPVideoSending();

                IsPinging = true;
                CheckAutoStart();
                ClickOnceUpdater.Restarting += (_1, _2) => { SaveSettings(); };
                tmrTime.Tick += (_1, _2) =>
                {
                    tmrTime.Stop();
                    if (Participant != null && Participant.State == CommunicationState.Opened)
                    {
                        Participant.TimeSignal(ServerName, DateTime.Now);
                    }
                    tmrTime.Start();
                };
                tmrTime.Start();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.StackTrace);
            }

            
        }

        public void StartPing()
        {
            try
            {
                ThreadPool.QueueUserWorkItem(new WaitCallback(ping));
            }
            catch (Exception)
            {
                
            }
            
        }

        void ping(object state)
        {
            try
            {
                while (isPinging)
                {
                    var t = Clients.Where(p => p.IsOnline == false).ToList();

                    foreach (var item in t)
                    {
                        CurrentDisp.Invoke((Action)(() => { Clients.Remove(item); }), null);
                    }
                    if (Participant != null && Participant.State == CommunicationState.Opened)
                    {
                        CurrentDisp.Invoke((Action)(() => { Participant.ActiveClientList(Clients.ToList()); }), null);
                    }

                    foreach (var item in Clients)
                    {
                        item.IsOnline = false;
                    }
                    if (Participant != null && Participant.State == CommunicationState.Opened)
                    {
                        CurrentDisp.Invoke((Action)(() => { Participant.Ping(); }), null);
                    }
                    Thread.Sleep(1000);
                }
            }
            catch(Exception ex)
            {
                
            }
        }

        public void Restart()
        {
            try
            {
                App.ViewModel.CloseModel();
                App.ViewModel.SaveSettings();
            }
            catch
            {
                
            }
            //Перезапускаем приложение
            System.Windows.Forms.Application.Restart();
            Environment.Exit(1);
        }

        private void CheckAutoStart()
        {
            //WaveIn
           // waveIn.Devices
            string path;
            if (ClickOnceUpdater.IsClickOnce) path = Properties.Settings.Default.StartPath;
            else path = Assembly.GetExecutingAssembly().Location;
            if (IsAutoStartEnable)
            {
                Utilities.AutoStartSettings.SetAutoStart(Properties.Settings.Default.ApplicationName, path);
            }
            else
                if (Utilities.AutoStartSettings.IsAutoStartEnabled(Properties.Settings.Default.ApplicationName, path))
                    Utilities.AutoStartSettings.UnSetAutoStart(Properties.Settings.Default.ApplicationName);
        }

        public void AddLogMessage(string strMessage)
        {
            try
            {
                Messages = Messages.Insert(0, DateTime.Now.ToString() + "\t" + strMessage + "\n");
            }
            catch (Exception)
            {

            }
            
        }
        
        public void SaveSettings()
        {
            Properties.Settings.Default.NetCameraSettings = NetCamera.GetSettings();
            Properties.Settings.Default.Save();
        }

        public void LoadSettings()
        {
            if (Properties.Settings.Default.NetCameraSettings == null) return;
            NetCamera.SetSettings(Properties.Settings.Default.NetCameraSettings as byte[]);
        }


        public void RunConferencService()
        {
            NoArgDelegate executor = new NoArgDelegate(this.runConferencService);
            executor.BeginInvoke(null, null);
        }

        private void runConferencService()
        {
            //Создание InstanceContext для обработки сообщений на интерфейсе обратного вызова. 
            //Создается экземпляр ChatApp и передается в InstanceContext.
             context = new InstanceContext(this);

            //Создание участника с заданной конфигурацией конечной точки
            //Каждый участник открывает дуплексный канал к сетке
            //участник - это экземпляр приложения разговора, открывший канал к сетке
             factory = new DuplexChannelFactory<IConferenceChannel>(context, "ChatEndpoint");
            participant = factory.CreateChannel();

           // //Получение PeerNode, связанного с участником, и регистрация событий в интерактивном и автономном режиме
           // //PeerNode представляет узел в сетке. Сетка - это именованная коллекция подключенных узлов.
           //// ostat = participant.GetProperty<IOnlineStatus>();

           // //TODO: Исправить на лямбда обработку!!!
           // //ostat.Offline += new EventHandler(ostat_Offline);
           // //ostat.Online += new EventHandler(ostat_Online);

            bool bIsConnected = false;
            while (!bIsConnected)
            {
                App.Log.Info("Подключение к серверу...");
                Console.WriteLine("Подключение к серверу...");
                AddLogMessage("Подключение к серверу...");
                try
                {
                   // participant = factory.CreateChannel();
                    participant.Open();
                    bIsConnected = true;
                    App.Log.Info("Подключение к серверу успешно!");
                    Console.WriteLine("Подключение к серверу успешно!");
                    AddLogMessage("Подключение к серверу успешно!");
                }
                catch (CommunicationException ex)
                {
                    App.Log.Error("Ошибка подключения к серверу!", ex);
                    Console.WriteLine("Ошибка подключения к серверу!");
                    AddLogMessage("Ошибка подключения к серверу!");
                    Thread.Sleep(1000);
                }
            }
            //Объявление себя другим участникам
            participant.Join(App.ViewModel.ServerName, App.ViewModel.MachineName, App.ViewModel.SoftWareVersion, App.ViewModel.IpAddres);
        }

        public void CloseConferencService()
        {
            //TODO: Протестировать закрытие. Закрыть, потом опять попытаться открыть...
            participant.Close();
          //  instanceContext = null;
            factory = null;
            ostat = null;
        }

        void ostat_Online(object sender, EventArgs e)
        {
            Console.WriteLine("ostat_Online");
            //throw new NotImplementedException();
        }

        void ostat_Offline(object sender, EventArgs e)
        {
            Console.WriteLine("ostat_Offline");

            //throw new NotImplementedException();
        }

        private void StartIPVideoSending()
        {
            try
            {
                ThreadPool.QueueUserWorkItem(new WaitCallback(IPVideoSending));
            }
            catch (Exception)
            {

            }
        }

        public void CloseModel()
        {
            try
            {
                if (waveIn != null)
                    waveIn.Dispose();
                waveIn = null;
            }
            catch (Exception)
            {
                
            }
            try
            {
                UDP_IP_AUDIO.Stop();
                UDP_IP_VIDEO.Stop();
            }
            catch (Exception)
            {
            }
        }

        private void IPVideoSending(object param)
        {
            try
            {
                while (IsIpVideoSending)
                {
                    Thread.Sleep(IPVideoSendInterval);
                    if (btIPVideoSample == null) continue;
                    IndexVideoMessage++;
                    var mes = new SendBytes()
                    {
                        From = ServerName,
                        To = new List<string>(),
                        Data = btIPVideoSample.Clone() as byte[],
                        IndexMessage = IndexVideoMessage,
                        TimeStamp = DateTime.Now
                    };

                    if (isUDP_Video == false)
                        Participant.VideoIPSample(mes);
                    else UDP_IP_VIDEO.SendBigBuffer(mes.Data);

                    lock (btIPVideoSample)
                    {
                        btIPVideoSample = null;
                    }
                }
            }
            catch(Exception ex)
            {
                
            }
        }

        #endregion

        #region Members

        private InstanceContext context;
        private NetPeerTcpBinding m_binding;
        private IOnlineStatus ostat = null;
        private DuplexChannelFactory<IConferenceChannel> factory;
        public DispatcherTimer tmrPing;// = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(1) };
        public DispatcherTimer tmrTime = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(10) };
        public Dispatcher CurrentDisp = Dispatcher.CurrentDispatcher;
        #endregion

        #region INotifyPropertyChanged Members
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        #endregion INotifyPropertyChanged Members

        #region WaveIn

        private int IndexAudioMessage;
        private int IndexVideoMessage;
        private WaveInRecorder waveIn;
        public WaveInRecorder WaveIn
        {
            get { return waveIn; }
            private set
            {
                if (waveIn == value) return;
                waveIn = value;
                waveIn.WaveRecorded += (_1, e) =>
                {
                    if (IpAudio==false) return;
                    if (Participant != null && Participant.State == CommunicationState.Opened)
                    {
                        IndexAudioMessage++;
                        if (bUDP_Audio)
                        {
                            UDP_IP_AUDIO.SendBigBuffer(VoiceCapturedEventArgs.Serialize(e));
                        }
                        else
                            Participant.AudioIPSample(
                                new SendBytes()
                            {
                                From = Room,
                                Data = VoiceCapturedEventArgs.Serialize(e),
                                IndexMessage = IndexAudioMessage,
                                TimeStamp = DateTime.Now,
                                To = new List<string>()
                            });
                        
                    }
                };
                OnPropertyChanged("WaveIn");
            }
        }
        #endregion

        #region IConference Members

        public void Join(string userName, string machineName, string softwareVersion, string strIpAddres)
        {
            ClientInfo cl = new ClientInfo(userName, machineName, softwareVersion, strIpAddres) { IsOnline = true };
            CurrentDisp.BeginInvoke((Action)(() => { Clients.Add(cl); }), null);
            CurrentDisp.BeginInvoke((Action)(() => { AddLogMessage("Клиент " + cl.UserName + " подкличился к сервису"); }), null);
        }

        public void Leave(string userName)
        {
            try
            {
                var clLeave = Clients.Where(p => p.UserName == userName).Single();
                CurrentDisp.BeginInvoke((Action)(() => { Clients.Remove(clLeave); }), null);
                AddLogMessage("Клиент " + clLeave.UserName + " отключился от сервиса");
            }
            catch(Exception ex)
            {
                
            }
        }

        public void Ping()
        {
            try
            {
                if (Participant != null) Participant.PingRequest(ServerName, MachineName, SoftWareVersion, IpAddres);
            }
            catch (Exception ex)
            {

            }
            
        }

        public void PingRequest(string userName, string machineName, string softwareVersion, string strIpAddres)
        {
            try
            {
                CurrentDisp.BeginInvoke((Action)(() =>
                {
                    if (Clients.Where(p => p.UserName == userName).Any())
                    {
                        Clients.Where(p => p.UserName == userName).First().IsOnline = true;
                    }
                    else
                    {
                        Clients.Add(new ClientInfo(userName, machineName, softwareVersion, strIpAddres) { IsOnline = true });
                    }
                }), null);
            }
            catch(Exception ex)
            {
                
            }
        }

        public void TextMessage(string From, List<string> To, string strMessage)
        {
            throw new NotImplementedException();
        }

        public void VideoSample(SendBytes sb)
        {
            if (sb.From == ServerName) return;
        }

        public void AudioSample(SendBytes sb)
        {
            if (sb.From == ServerName) return;
        }

        public void AudioIPSample(SendBytes sendBytes)
        {
            if (sendBytes.From == ServerName) return;
          //  throw new NotImplementedException();
        }

        public void ActiveClientList(List<ClientInfo> clientList)
        {
            return;
        }

        public void TimeSignal(string From, DateTime Time)
        {
            return;
        }

        public void TimeSignalRequest()
        {
            if (Participant != null && Participant.State == CommunicationState.Opened)
            {
                Participant.TimeSignal(ServerName, DateTime.Now);
                AddLogMessage("Переданы сигналы точного времени");
            }
        }

        public void VideoIPSample(SendBytes message) { }

        //public void AudioIPSample(SendBytes sb) { }

        public void DeviceControlLights(string From, byte[] btLights) { }

        public void BeginCall(string From, string To)
        {
            string message = "Клиент " + From + " ЗАПРОСИЛ конференцию с клиентом " + To;
            AddLogMessage(message);
            App.Log.Info(message);
        }

        public void CancelCall(string From, string To)
        {
            string message = "Клиент " + From + " Отказался от конференцию с клиентом " + To;
            AddLogMessage(message);
            App.Log.Info(message);
        }


        public void StartConference(string From, string To)
        {
            string message = "Клиент " + From + " начал конференцию с клиентом " + To;
            AddLogMessage(message);
            App.Log.Info(message);
        }

        public void StopConference(string From, string To)
        {
            string message = "Клиент " + From + " закончил конференцию с клиентом " + To;
            AddLogMessage(message);
            App.Log.Info(message);
        }

        #endregion

    }
}
